import json
import os
import tkinter as tk
from tkinter import ttk
from tkinter import filedialog
from tkinter import Menu
from tkinter import scrolledtext
import tkinter.font as tkfont
import threading
import ctypes

import time

class xrn_rendering_options:
    defaultfontfamily = "Courier"
    #defaultfontsize = 10
    defaultfontsize = 8
    maxtextwidth = 320
    indentationstep = 40
    comboboxsize = 400
    buttonwidth = 110
    pathwidth = comboboxsize
    stringwidth = comboboxsize
    rowheight = 18
    framerowheight = rowheight + 2
    geometry = "800x800"
    minsize = (comboboxsize + maxtextwidth + buttonwidth + indentationstep*3 , 400)

class ScrollableFrame( ttk.Frame ):
    def __init__(self, container, *args, **kwargs ):
        super().__init__(container,  *args, **kwargs )
        
        # Create a canvas
        self.canvas = tk.Canvas(self)
        self.canvas.pack(side="left", fill="both", expand=True)
        self.scroll_enabled = True        

        # Add a vertical scrollbar
        self.scrollbar = ttk.Scrollbar(self, orient="vertical", command=self.canvas.yview)
        self.scrollbar.pack(side="right", fill="y")
        self.scrollbar.bind("<Enter>", self.enable_scrolling_focus )

        # Configure the canvas with the scrollbar
        self.canvas.configure(yscrollcommand=self.scrollbar.set)
        
        # Create an internal frame to hold the widgets
        self.scrollable_frame = ttk.Frame(self.canvas)
        
        # Bind the internal frame to update scroll region
        self.scrollable_frame.bind(
            "<Configure>",
            lambda e: self.canvas.configure(
                scrollregion=self.canvas.bbox("all")
            )
        )
        
        # Create a window in the canvas to hold the internal frame
        self.canvas.create_window((0, 0), window=self.scrollable_frame, anchor="nw")
        
        # Bind canvas resize event to adjust scrollable_frame width
        self.canvas.bind("<Configure>", self._on_canvas_configure)
        
        # Optional: Bind the mouse wheel to scroll the canvas
        self.scrollable_frame.bind("<Enter>", lambda e: self._bind_mousewheel(self.canvas))
        self.scrollable_frame.bind("<Leave>", lambda e: self._unbind_mousewheel(self.canvas))
        self.scrollable_frame.bind_all("<Button-4>", self._on_mousewheel) 
        self.scrollable_frame.bind_all("<Button-5>", self._on_mousewheel) 

    def _on_canvas_configure(self, event):

        canvas_width = event.width
        self.canvas.itemconfig(self.canvas.create_window((0, 0), window=self.scrollable_frame, anchor="nw"), width=canvas_width)
        self.canvas.configure(scrollregion=self.canvas.bbox("all"))

    def _bind_mousewheel(self, canvas):
        canvas.bind_all("<MouseWheel>", self._on_mousewheel)

    def _unbind_mousewheel(self, canvas):
        canvas.unbind_all("<MouseWheel>")

    def _on_mousewheel(self, event):
        if self.scroll_enabled == True :
            if event.num == 5 or event.delta < 0:
                self.canvas.yview_scroll(1, "units")
            elif event.num == 4 or event.delta > 0:
                self.canvas.yview_scroll(-1, "units")

    def disable_scrolling( self ):
        self.scroll_enabled = False

    def enable_scrolling_focus( self, event ):
        self.scroll_enabled = True

    def enable_scrolling( self ):
        self.scroll_enabled = True


class xrn_main_window :

    def __init__(self, parent, json_str, geometry=None ):
        self.root = parent
        self.pre_thread_layout = ""
        self.parent = parent
        self.parent.rownumber = 0

        if geometry:
            parent.geometry(geometry)
        else:
            parent.geometry(xrn_rendering_options.geometry)

        parent.minsize(xrn_rendering_options.minsize[0], xrn_rendering_options.minsize[1])

        if json_str == "" :
            self.jlayout_str = get_jlayout()
        else :
            self.jlayout_str = json_str

        parent.grid_columnconfigure(0, weight=1)
        
        self.paste = Menu(parent, tearoff=0)
        self.paste.add_command(label="Paste", command=self.paste_text)
        self.paste_id = ""
        self.paste_target = ""

        self.copy_cut_paste = Menu(parent, tearoff=0)
        self.copy_cut_paste.add_command(label="Cut", command=self.cut_text)
        self.copy_cut_paste.add_command(label="Copy", command=self.copy_text)
        self.copy_cut_paste.add_command(label="Paste", command=self.paste_text)
        
        parent.bind("<Button-1>", self.hide_copy_cut_paste) 

        if validate_layout(self.jlayout_str, [], []) == 0:
            self.scrollable_frame = ScrollableFrame( parent )
            self.scrollable_frame.scrollable_frame.rownumber = 0
            self.parse_json( self.scrollable_frame.scrollable_frame, self.jlayout_str )
        else:
            print("invalid layout")

    def destroy(self) :
        self.scrollable_frame.destroy()

    def hide(self) :
        self.scrollable_frame.pack_forget()

    def show(self) :
        self.scrollable_frame.pack(side="right", fill="both", expand=True, anchor="ne")

    def get_geometry(self):
        return self.root.geometry()

    def on_checkbox_change( self, idstr ) :
        jrow_obj = get_from_layout(self.jlayout_str, idstr)

        if jrow_obj["fields"][2]["checkbox"] == "enable" :
            jrow_obj["fields"][2]["checkbox"] = "disable" 

        elif jrow_obj["fields"][2]["checkbox"] == "disable" :
            jrow_obj["fields"][2]["checkbox"] = "enable" 

        self.jlayout_str = set_to_layout(self.jlayout_str, jrow_obj)

    def open_show_archive( self, idstr ):
        jrow_obj = get_from_layout(self.jlayout_str, idstr)

        # look for archive

        field_index = 0
        found_combobox = 0
        combobox_field = 0
        item_field = 0
        item_index = 0
        item_found = 0

        for field in jrow_obj["fields"] :
            if field["type"] == "archive" :
                for item in field["items"] :
                    if item["display"] == jrow_obj["fields"][2]["selection"] :
                        found_combobox = 1
                        item_found = 1
                        item_index = item_field
                        combobox_field = field_index

                    item_field = item_field + 1
            field_index = field_index + 1

        # set new selection
        if (found_combobox == 1) and (item_found == 1) :
            text = jrow_obj["fields"][combobox_field]["items"][item_index]["data"]
            open_text_popup(text)

    def freze_release_gui_on_button_pressed( self, function_name ) :

        local_layout = self.jlayout_str
        self.parent.pre_thread_layout = self.jlayout_str

        function = globals().get(function_name)
        local_footprint = build_id_trees_layout(self.jlayout_str, "")

        display_name = ""

        for rows in local_footprint.split("\n") : 
            for ids in rows.split(" "):
                obj = get_from_layout( local_layout, ids )
                if ( obj["type"] == "row" ) :
                    obj["permission"] = "read_only"
                    local_layout = set_to_layout( local_layout, obj )
                    if "function" in obj["fields"][2] :
                        if obj["fields"][2]["function"] == function_name :
                            display_name = obj["fields"][0]["display"]
                elif ( obj["type"] == "rows" ) :
                    obj["expand"] = "off"
                    local_layout = set_to_layout( local_layout, obj )

        thread_obj = {}
        thread_obj["type"] = "rows"
        thread_obj["display"] = "execution control panel"
        thread_obj["id"] = "thread_control_panel_id"
        thread_obj["expand"] = "on"
        thread_obj["rows"] = []

        thread_obj["rows"].append({})
        thread_obj["rows"][0]["type"] = "row"
        thread_obj["rows"][0]["id"] = "thread_obj_id"
        thread_obj["rows"][0]["permission"] = "read_write"
        thread_obj["rows"][0]["fields"] = []
        thread_obj["rows"][0]["fields"].append({ "type": "textbox", "display": display_name })
        thread_obj["rows"][0]["fields"].append({ "type": "void" })
        thread_obj["rows"][0]["fields"].append({ "type": "button", "thread": "off", "display" : "stop", "function": "stop_thread_layout" })

        closed_layout_with_thread_obj = []
        closed_layout_with_thread_obj.append(thread_obj)

        closed_layout = json.loads(local_layout)
        for obj in closed_layout :
            closed_layout_with_thread_obj.append(obj)

        layout_with_thread_str = json.dumps( closed_layout_with_thread_obj )
        render_layout(  layout_with_thread_str )

        try :
            result_archive_str = function( )

            if is_archive_only( result_archive_str ) == 1 :

                thread_obj["rows"][0]["fields"][2] = { "type": "button", "thread": "off", "display" : "return", "function": "reload_pre_thread_layout" }

                thread_obj["rows"].append( {} )
                thread_obj["rows"][1]["type"] = "row"
                thread_obj["rows"][1]["id"] = "layout_store_results_thread_path_id"
                thread_obj["rows"][1]["permission"] = "read_write"
                thread_obj["rows"][1]["fields"] = []
                thread_obj["rows"][1]["fields"].append({ "type": "textbox", "display": "store result path" })
                thread_obj["rows"][1]["fields"].append({ "type": "void" })
                thread_obj["rows"][1]["fields"].append({ "type": "path", "display": "", "target" : "file", "direction": "output" })

                thread_obj["rows"].append({})
                thread_obj["rows"][2]["type"] = "row"
                thread_obj["rows"][2]["id"] = "layout_store_results_thread_button_id"
                thread_obj["rows"][2]["permission"] = "read_write"
                thread_obj["rows"][2]["fields"] = []
                thread_obj["rows"][2]["fields"].append({ "type": "textbox", "display": "store result" })
                thread_obj["rows"][2]["fields"].append({ "type": "void" })
                thread_obj["rows"][2]["fields"].append({ "type": "button", "thread": "off", "display" : "store", "function": "layout_store_results_thread" })

                thread_obj["rows"].append( json.loads( result_archive_str )[0] )
                thread_obj["rows"][3]["id"] = "renamed_archive_id_thread_response_id"

                closed_layout_with_thread_obj = []
                closed_layout_with_thread_obj.append(thread_obj)

                closed_layout = json.loads(local_layout)
                for obj in closed_layout :
                    closed_layout_with_thread_obj.append(obj)

                layout_with_thread_str = json.dumps( closed_layout_with_thread_obj )
                render_layout(  layout_with_thread_str )

                while(self.parent.pre_thread_layout != "" ) :
                    time.sleep(1)

        except :
            open_text_popup( display_name + " error " )
            render_layout( self.parent.pre_thread_layout )

        if self.parent.pre_thread_layout != "" :
            render_layout( self.parent.pre_thread_layout )

        self.parent.pre_thread_layout = ""


    def load_pre_thread_layout( self ) :
        if self.parent.pre_thread_layout != "" :
            render_layout(  self.parent.pre_thread_layout )
            self.parent.pre_thread_layout = ""


    def stop_process(self):

        active_threads = threading.enumerate()

        thread_id = -1
        gotten_thread = None

        for thread in active_threads:
            if "freze_release_gui_on_button_pressed" in thread.name :
                thread_id = int(thread.ident)
                gotten_thread = thread

        if gotten_thread.is_alive() :
            if self.parent.pre_thread_layout != "" :
                render_layout(  self.parent.pre_thread_layout )
                self.parent.pre_thread_layout = ""
            ctypes.pythonapi.PyThreadState_SetAsyncExc(ctypes.c_long(thread_id), ctypes.py_object(SystemExit))

    def update_combobox_jlayout( self, combobox_str, idstr, typeobj ):

        jrow_obj = get_from_layout(self.jlayout_str, idstr)
        jcombobox_obj = json.loads(combobox_str)

        # look for combobox

        field_index = 0
        found_combobox = 0
        combobox_field = 0

        for field in jrow_obj["fields"] :
            if field["type"] == typeobj :
                for item in field["items"] :
                    if item["display"] == jcombobox_obj["selection"] :
                        found_combobox = 1
                        combobox_field = field_index

            field_index = field_index + 1

        # set new selection
        if found_combobox == 1 :

            jrow_obj["fields"][combobox_field]["selection"] = jcombobox_obj["selection"]
            self.jlayout_str = set_to_layout(self.jlayout_str, jrow_obj)

    def update_display_jlayout( self, strvalue, idstr, typestr ):

        jrow_obj = get_from_layout(self.jlayout_str, idstr)
        fieldi = 0
        for field in jrow_obj["fields"] :
            if field["type"] == typestr :
                jrow_obj["fields"][fieldi]["display"] = strvalue
            fieldi = fieldi + 1
        self.jlayout_str = set_to_layout(self.jlayout_str, jrow_obj)


    def update_panel_jlayout( self, strvalue, idstr ):

        jrow_obj = get_from_layout(self.jlayout_str, idstr)
        jrow_obj["expand"] = strvalue
        self.jlayout_str = set_to_layout(self.jlayout_str, jrow_obj)


    def enter_frame (self, frame ):
         frame.configure( relief="sunken" )

    def leave_frame (self, frame ):
         frame.configure( relief="flat" )

    def parse_json(self, parent, jlayout_str ):

        indentation = xrn_rendering_options.indentationstep 
        jlayout_obj = json.loads( jlayout_str )

        parent.grid_columnconfigure(0, weight=1)

        for element in jlayout_obj:

            if element["type"] == "rows":

                new_frame = tk.Frame(parent, relief="flat", borderwidth=0)
                new_frame.parent = parent
                new_frame.rownumber = 0
                new_frame.grid(row=parent.rownumber + 1, column=0, sticky="we",padx=(indentation,0))
                new_frame.columnconfigure(0,weight=1)
                new_frame.rowconfigure(0,weight=1)
                
                label_frame = tk.Frame(parent)
                label_frame.grid(row=parent.rownumber, column=0, sticky="ws")

                plusminustxt = '-' if element['expand'] == 'on' else '+'
                plusminuslabel = tk.Label(label_frame, text=plusminustxt, height=2,width=2, font=tkfont.Font(family=xrn_rendering_options.defaultfontfamily, size=xrn_rendering_options.defaultfontsize))
                plusminuslabel.grid(row=0,column=0)

                label_txt = element['display']
                label = tk.Label(label_frame, text=label_txt, height=2, font=tkfont.Font(family=xrn_rendering_options.defaultfontfamily, size=xrn_rendering_options.defaultfontsize))
                label.grid(row= 0,column=1)
                parent.rownumber += 2

                if element["expand"] == "off" :
                    new_frame.grid_remove()

                label.bind( "<Button-1>", 
                            lambda event, frame=new_frame, lbl=label, pmlbl=plusminuslabel, idp=element["id"], txt=label_txt: 
                            self.toggle_visibility(frame, lbl, pmlbl, idp, txt))

                plusminuslabel.bind( "<Button-1>", 
                            lambda event, frame=new_frame, lbl=label, pmlbl=plusminuslabel, idp=element["id"], txt=label_txt: 
                            self.toggle_visibility(frame, lbl, pmlbl, idp, txt))


                for row in element["rows"]:
                    
                    row_frame = tk.Frame(new_frame, borderwidth=1,height=xrn_rendering_options.rowheight+2)


                    row_frame.grid(row=parent.rownumber, column=0,sticky="we")
                    row_frame.columnconfigure(0,weight=1)
                    row_frame.rownumber = 0
                    parent.rownumber += 1

                    if row["type"] == "row":
                        self._parse_row(row_frame, row, row_frame, jlayout_str)
                        row_frame.bind('<Enter>', lambda event, frame=row_frame: self.enter_frame(frame))
                        row_frame.bind('<Leave>', lambda event, frame=row_frame: self.leave_frame(frame))
                    
                    elif row["type"] == "rows":
                         jlayout_sub_str = "[ " + json.dumps(get_from_layout(jlayout_str, row["id"])) + " ] "
                         self.parse_json(row_frame, jlayout_sub_str )


    def _parse_row(self, frame, row, row_frame, jlayout_str ):

        row_index = 0
        permission = row["permission"]
        rowid = row["id"]

        event_el = []
        for field in row["fields"]:
            tp = field["type"]

            if tp == "textbox":
                element = textbox(frame, field, row_index )
                

            elif tp == "string":
                element = string  ( frame, permission, row_index, field, rowid, self )

            elif tp == "combobox":
                element = combobox(frame, field, permission, row_index, jlayout_str, rowid, self )

            elif tp == "archive":
                element = archive( frame, field, permission, row_index, jlayout_str, rowid, self )

            elif tp == "button":
                element = button  ( frame, field, permission, row_index, self )

            elif tp == "void":
                element = space   ( frame, row_index )

            elif tp == "path":
                element = path(field, permission, frame, row_index, field["display"], rowid, self)

            else:
                print(f"unknown field type: {field['type']}")

            row_index += 1
        

    def toggle_visibility(self, panel, label, plusminuslabel, idl, txt):
        if panel.winfo_ismapped():
            plusminuslabel.config(text="+")
            label.config(text=txt)
            self.update_panel_jlayout("off", idl)
            panel.grid_remove()
        else:
            plusminuslabel.config(text="-")
            label.config(text=txt)
            self.update_panel_jlayout("on", idl)
            panel.grid()

    def show_paste(self,event,text_box, idstr,target):
        self.paste.post(event.x_root, event.y_root)
        self.text_box = text_box 
        self.paste_id = idstr
        self.paste_target = target
    
    def show_copy_cut_paste(self,event,text_box, idstr,target):
        self.copy_cut_paste.post(event.x_root, event.y_root)
        self.text_box = text_box 
        self.paste_id = idstr
        self.paste_target = target

    def hide_copy_cut_paste(self,event):
        self.paste.unpost()
        self.copy_cut_paste.unpost()

    def cut_text(self):
        self.text_box.event_generate("<<Cut>>")

    def copy_text(self):
        self.text_box.event_generate("<<Copy>>")

    def paste_text(self):
        self.text_box.event_generate("<<Paste>>")
        self.update_display_jlayout(self.text_box.get(), self.paste_id, self.paste_target) 

class textbox:

    def __init__(self, frame, field, row_index ):

        frame.columnconfigure(row_index,weight=0)

        subframe = tk.Frame(frame,width=xrn_rendering_options.maxtextwidth,height=xrn_rendering_options.rowheight)
        subframe.grid(row=0, column=row_index,sticky="we")
        subframe.grid_propagate(False)

        self.name = field["display"]

        self.label = tk.Label( subframe, 
                          font=tkfont.Font(family=xrn_rendering_options.defaultfontfamily, size=xrn_rendering_options.defaultfontsize),
                          text="  "+self.name)

        self.label.grid(row=0, column=0,sticky="w")


class space:

    def __init__(self, frame, row_index):
        frame.columnconfigure(row_index,weight = 1)

        self.spaceframe = tk.Frame(frame, width=30)
        self.spaceframe.grid( row=0,
                    column=row_index,sticky="nswe")
        self.spaceframe.columnconfigure(0,weight=1)
        self.spaceframe.columnconfigure(1,weight=1)
        self.spaceframe.rowconfigure(0,weight=1)
    

class combobox:

    def __init__(self, frame, field, permission, row_index, jlayout_str, rowid, parent):

        self.parent = parent  # Save the parent reference
        permission = tk.NORMAL if permission == "read_write" else tk.DISABLED
        self.field = field

        selection = next((i for i, item in enumerate(field["items"]) if item["display"] == field["selection"]), 0)
        self.items = field["items"]

        subframe = tk.Frame(frame)
        subframe.grid(row=0, column=row_index,sticky="e")
        
        function = globals().get(field["function"])

        options = [item["display"] for item in self.items]
        
        subframe.columnconfigure(0,weight=1)
        subframecombobox= tk.Frame(subframe,width=xrn_rendering_options.comboboxsize,height=xrn_rendering_options.rowheight)
        subframecombobox.grid(row=0, column=0,sticky="nswe")
        subframecombobox.columnconfigure(0,weight=1)
        subframecombobox.rowconfigure(0,weight=1)
        subframecombobox.grid_propagate(False)

        if permission == tk.NORMAL:
            permission_combo = "readonly"
        else : 
            permission_combo = tk.DISABLED

        self.combo = ttk.Combobox( subframecombobox, 
                                   values=options, width=xrn_rendering_options.comboboxsize,height=xrn_rendering_options.rowheight,
                                   font=tkfont.Font(family=xrn_rendering_options.defaultfontfamily, size=xrn_rendering_options.defaultfontsize),
                                   state=permission_combo)
        
        self.combo.grid(row=0, column=0,sticky="nswe")
        self.combo.current(selection)

        self.combo.bind("<<ComboboxSelected>>", 
                         lambda event, 
                         idstr=rowid: self.on_combobox_change(event, idstr))
        
        self.combo.bind("<Button-5>", lambda event: self.block_scroll() )
        self.combo.bind("<Button-4>", lambda event: self.block_scroll() )
        self.combo.bind("<Button-1>", lambda event: self.parent.scrollable_frame.disable_scrolling() )
        self.combo.bind("<FocusOut>", lambda event: self.parent.scrollable_frame.disable_scrolling() )
        self.combo.bind("<FocusIn>", lambda event: self.parent.scrollable_frame.enable_scrolling() )

        subframe_element = tk.Frame( subframe, width=xrn_rendering_options.buttonwidth,height=xrn_rendering_options.rowheight )
        subframe_element.grid(row=0, column=1,sticky="nswe")
        subframe_element.columnconfigure(0,weight=1)
        subframe_element.rowconfigure(0,weight=1)
        subframe_element.grid_propagate(False)
        
        if field["button"] == "on":

            self.text_var = tk.StringVar()
            self.text_var.set("updated")

            self.button_widget = tk.Button(subframe_element, 
                                            textvariable=self.text_var, 
                                            compound="c", padx=0, pady=0,
                                            state=permission, 
                                            font=tkfont.Font(family=xrn_rendering_options.defaultfontfamily, size=xrn_rendering_options.defaultfontsize),
                                            command=function)

            self.button_widget.grid(row=0, column=0,sticky="nswe")

        elif field["checkbox"] != "off" :

            self.combo_widget = ttk.Combobox( subframe_element, 
                                       values=["enable", "disable"], 
                                       font=tkfont.Font(family=xrn_rendering_options.defaultfontfamily, size=xrn_rendering_options.defaultfontsize),
                                       state=permission_combo, width=xrn_rendering_options.comboboxsize)

            self.combo_widget.grid(row=0, column=0,sticky="nswe")

            if field["checkbox"] == "enable" :
                self.combo_widget.current(0)
            else :
                self.combo_widget.current(1)

            self.combo_widget.bind("<<ComboboxSelected>>", 
                             lambda event, 
                             idstr=rowid: self.parent.on_checkbox_change(idstr))

            self.combo_widget.bind("<Button-1>", lambda event: self.parent.scrollable_frame.disable_scrolling() )
            self.combo_widget.bind("<FocusOut>", lambda event: self.parent.scrollable_frame.disable_scrolling() )
            self.combo_widget.bind("<FocusIn>", lambda event: self.parent.scrollable_frame.enable_scrolling() )
            self.combo_widget.bind("<Button-5>", lambda event: self.block_scroll() )
            self.combo_widget.bind("<Button-4>", lambda event: self.block_scroll() )

    def get_content(self):
        return self.combo.get()

    def block_scroll( self ):
        return "break"

    def on_combobox_change(self, event, idstr):
        if self.field["button"] == "on" :
            self.text_var.set("refresh !")

        for item in self.items:
            if self.get_content() == item["display"]:
                self.field["selection"] = self.get_content()
                self.parent.update_combobox_jlayout( json.dumps(self.field), idstr, "combobox" )


class string:

    def __init__(self, frame, permission, row_index, field, rowid, parent):
        visibility = field["visibility"]
        display = field["display"]
        permission = tk.NORMAL if permission == "read_write" else tk.DISABLED
        self.parent = parent
        
        
        self.entry_var = tk.StringVar(value=display)
        function = globals().get(field["function"])

        if permission == tk.NORMAL:
            permission_combo = "readonly"
        else : 
            permission_combo = tk.DISABLED
        
        subframe = tk.Frame(frame)
        subframe.grid(row=0, column=row_index,sticky="e")
        
        subframe_entry = tk.Frame( subframe, width=xrn_rendering_options.stringwidth,height=xrn_rendering_options.rowheight )
        subframe_entry.grid(row=0, column=0,sticky="nswe")
        subframe_entry.grid_propagate(False) 
        subframe_entry.columnconfigure(0,weight=1)
        subframe_entry.rowconfigure(0,weight=1)
        
        if visibility == "open":
            self.entry = tk.Entry(subframe_entry, textvariable=self.entry_var, state=permission, font=tkfont.Font(family=xrn_rendering_options.defaultfontfamily, size=xrn_rendering_options.defaultfontsize))
            self.entry.bind("<Button-3>", lambda event,  entry = self.entry, idstr=rowid, target="string": parent.show_copy_cut_paste(event,entry,idstr,target)) 
        else:
            self.entry = tk.Entry(subframe_entry, textvariable=self.entry_var, show="*", state=permission, font=tkfont.Font(family=xrn_rendering_options.defaultfontfamily, size=xrn_rendering_options.defaultfontsize))
            self.entry.bind("<Button-3>", lambda event, entry=self.entry, idstr=rowid, target="string": parent.show_paste(event,entry,idstr,target)) 

        self.entry.grid(row=0, column=0,sticky="nswe")
        self.entry.xview("end")

        self.entry.bind("<Button-4>", lambda event: self.parent.scrollable_frame.enable_scrolling() )
        self.entry.bind("<Button-5>", lambda event: self.parent.scrollable_frame.enable_scrolling() )

        #Insert a virtual Button (frame) as space  
        
        subframe_button = tk.Frame( subframe, width=xrn_rendering_options.buttonwidth,height=xrn_rendering_options.rowheight )
        subframe_button.grid(row=0, column=1,sticky="nswe")
        subframe_button.grid_propagate(False)

        self.entry.bind("<KeyRelease>", lambda event, idstr=rowid: self.on_entry_change(idstr))
        
        subframe_element = tk.Frame( subframe, width=xrn_rendering_options.buttonwidth,height=xrn_rendering_options.rowheight )
        subframe_element.grid(row=0, column=1,sticky="nswe")
        subframe_element.columnconfigure(0,weight=1)
        subframe_element.rowconfigure(0,weight=1)
        subframe_element.grid_propagate(False)
        
        if field["button"] == "on":

            if field["thread"] == "on" :        
                thread = threading.Thread(target=parent.freze_release_gui_on_button_pressed, args=(field["function"],))
                self.button_widget = tk.Button( subframe_element, 
                                           text=field["button_display"], 
                                           compound="c", padx=0, pady=0,
                                           state=permission, 
                                           font=tkfont.Font(family=xrn_rendering_options.defaultfontfamily, size=xrn_rendering_options.defaultfontsize), 
                                           command=lambda : thread.start() )
            else :
                function = globals().get(field["function"])
                self.button_widget = tk.Button( subframe_element, 
                                           text=field["button_display"], 
                                           compound="c", padx=0, pady=0,
                                           state=permission, 
                                           font=tkfont.Font(family=xrn_rendering_options.defaultfontfamily, size=xrn_rendering_options.defaultfontsize), 
                                           command=function )

            self.button_widget.grid(row=0, column=0,sticky="nswe")

        elif field["checkbox"] != "off" :

            self.combo_widget = ttk.Combobox( subframe_element, 
                                       values=["enable", "disable"], 
                                       font=tkfont.Font(family=xrn_rendering_options.defaultfontfamily, size=xrn_rendering_options.defaultfontsize), 
                                       state=permission_combo ,width=xrn_rendering_options.comboboxsize)

            self.combo_widget.grid(row=0, column=0,sticky="nswe")

            if field["checkbox"] == "enable" :
                self.combo_widget.current(0)

            else :
                self.combo_widget.current(1)

            self.combo_widget.bind("<<ComboboxSelected>>", 
                             lambda event, 
                             idstr=rowid: self.parent.on_checkbox_change(idstr))

            self.combo_widget.bind("<Button-1>", lambda event: self.parent.scrollable_frame.disable_scrolling() )
            self.combo_widget.bind("<FocusOut>", lambda event: self.parent.scrollable_frame.disable_scrolling() )
            self.combo_widget.bind("<FocusIn>", lambda event: self.parent.scrollable_frame.enable_scrolling() )
            self.combo_widget.bind("<Button-5>", lambda event: self.block_scroll() )
            self.combo_widget.bind("<Button-4>", lambda event: self.block_scroll() )
            

    def on_entry_change(self, idstr):
        self.parent.update_display_jlayout(self.get_content(), idstr, "string")

    def block_scroll( self ):
        return "break"

    def get_content(self):
        return self.entry_var.get()


class path:

    def __init__(self, field, permission, frame, row_index, display, rowid, parent):
        permission = tk.NORMAL if permission == "read_write" else tk.DISABLED

        if display != "" :
            display = os.path.abspath(os.path.expanduser(display))

        self.field = field
        target = self.field["target"]
        direction = self.field["direction"]
        self.parent = parent
        frame.rowconfigure(row_index,weight=1)
        subframe = tk.Frame(frame)
        subframe.grid(row=0, column=row_index,sticky="nswe")
        subframe.rowconfigure(0,weight=1)
        subframe.columnconfigure(0,weight=1)
        subframe.columnconfigure(1,weight=1)
        
        # Create Entry and store it as an instance variable
        subframe_entry = tk.Frame( subframe, width=xrn_rendering_options.pathwidth,height=xrn_rendering_options.rowheight )
        subframe_entry.grid(row=0, column=0,sticky="nswe")
        subframe_entry.grid_propagate(False) 
        subframe_entry.columnconfigure(0,weight=1)
        subframe_entry.rowconfigure(0,weight=1)

        self.entry_var = tk.StringVar(value=display)
        self.entry = tk.Entry(subframe_entry, textvariable=self.entry_var, state=permission, font=tkfont.Font(family=xrn_rendering_options.defaultfontfamily, size=xrn_rendering_options.defaultfontsize))
        self.entry.bind("<KeyRelease>", lambda event, idstr=rowid: self.on_entry_change(idstr))
        self.entry.grid(row=0, column=0,sticky="nswe")

        self.entry.bind("<Button-4>", lambda event: self.parent.scrollable_frame.enable_scrolling() )
        self.entry.bind("<Button-5>", lambda event: self.parent.scrollable_frame.enable_scrolling() )

        self.entry.bind("<Button-3>", lambda event,  entry = self.entry, idstr=rowid, target="path": parent.show_copy_cut_paste(event,entry,idstr,target)) 
        self.entry.xview("end")

        subframe_button = tk.Frame(subframe,width=xrn_rendering_options.buttonwidth,height=xrn_rendering_options.rowheight)
        subframe_button.grid(row=0, column=1,sticky="nswe")
        subframe_button.columnconfigure(0,weight=1)
        subframe_button.rowconfigure(0,weight=1)

        subframe_button.grid_propagate(False)

        if target == "file":
            if direction == "input":
                button_widget = tk.Button(subframe_button, 
                                          command=lambda idstr=rowid: self.select_file_input(idstr), 
                                          compound="c", padx=0, pady=0,
                                          state=permission, 
                                          font=tkfont.Font(family=xrn_rendering_options.defaultfontfamily, size=xrn_rendering_options.defaultfontsize), 
                                          text="in file")
            if direction == "output":
                button_widget = tk.Button(subframe_button, 
                                          command=lambda idstr=rowid: self.select_file_output(idstr), 
                                          compound="c", padx=0, pady=0,
                                          state=permission, 
                                          font=tkfont.Font(family=xrn_rendering_options.defaultfontfamily, size=xrn_rendering_options.defaultfontsize), 
                                          text="out file")
        elif target == "directory":
            # Use lambda without event for the button command
            button_widget = tk.Button(subframe_button, 
                                      command=lambda idstr=rowid: self.select_directory(idstr), 
                                      compound="c", padx=0, pady=0,
                                      state=permission, 
                                      font=tkfont.Font(family=xrn_rendering_options.defaultfontfamily, size=xrn_rendering_options.defaultfontsize), 
                                      text="folder")

        button_widget.grid(row=0, column=0,sticky="nswe")

    def on_entry_change(self, idstr):
        self.parent.update_display_jlayout(self.get_content(), idstr, "path")

    def select_file_input(self, idstr):
        file_path = filedialog.askopenfilename(title="select input file")
        if file_path:
            self.entry.delete(0, tk.END)
            self.entry.insert(0, file_path)
            self.on_entry_change(idstr)


    def select_file_output(self, idstr):
        file_path = filedialog.asksaveasfilename(title="select output file")
        if file_path:
            self.entry.delete(0, tk.END)
            self.entry.insert(0, file_path)
            self.on_entry_change(idstr)


    def select_directory(self, idstr):
        dir_path = filedialog.askdirectory(title="select a directory")
        if dir_path :
            self.entry.delete(0, tk.END)
            self.entry.insert(0, dir_path)
            self.on_entry_change(idstr)

    def block_scroll( self ):
        return "break"

    def get_content(self):
        return self.entry_var.get()


class button :

    def __init__(self, frame, field, permission, row_index, parent ):

        permission = tk.NORMAL if permission == "read_write" else tk.DISABLED
        name = field["display"]

        frame.rowconfigure(row_index,weight=1)

        subframe = tk.Frame(frame,width=xrn_rendering_options.buttonwidth,height=xrn_rendering_options.rowheight)
        subframe.grid(row=0, column=row_index,sticky="nswe")
        subframe.columnconfigure(0,weight=1)
        subframe.rowconfigure(0,weight=1)
        subframe.grid_propagate(False) 

        if field["thread"] == "on" :        
            thread = threading.Thread(target=parent.freze_release_gui_on_button_pressed, args=(field["function"],))
            button_widget = tk.Button( subframe, 
                                       text=name, 
                                       compound="c", padx=0, pady=0,
                                       state=permission, 
                                       font=tkfont.Font(family=xrn_rendering_options.defaultfontfamily, size=xrn_rendering_options.defaultfontsize), 
                                       command=lambda : thread.start() )
        else :
            function = globals().get(field["function"])
            button_widget = tk.Button( subframe, 
                                       text=name, 
                                       compound="c", padx=0, pady=0,
                                       state=permission, 
                                       font=tkfont.Font(family=xrn_rendering_options.defaultfontfamily, size=xrn_rendering_options.defaultfontsize), 
                                       command=function )

        button_widget.grid(row=0, column=0,sticky="nswe")


class archive:

    def __init__(self, frame, field, permission, row_index, jlayout_str, rowid, parent):

        self.parent = parent  # Save the parent reference
        permission = tk.NORMAL if permission == "read_write" else tk.DISABLED
        self.field = field

        selection = next((i for i, item in enumerate(field["items"]) if item["display"] == field["selection"]), 0)
        self.items = field["items"]

        subframe = tk.Frame(frame)
        subframe.grid(row=0, column=row_index,sticky="e")
        
        options = [item["display"] for item in self.items]
        
        subframe.columnconfigure(0,weight=1)
        subframecombobox= tk.Frame(subframe,width=xrn_rendering_options.comboboxsize,height=xrn_rendering_options.rowheight)
        subframecombobox.grid(row=0, column=0,sticky="we")
        subframecombobox.columnconfigure(0,weight=1)
        subframecombobox.rowconfigure(0,weight=1)
        subframecombobox.grid_propagate(False)

        if permission == tk.NORMAL:
            permission_combo = "readonly"

        else : 
            permission_combo = tk.DISABLED

        self.combo = ttk.Combobox( subframecombobox, 
                                   values=options, width=xrn_rendering_options.comboboxsize,height=xrn_rendering_options.rowheight,
                                   font=tkfont.Font(family=xrn_rendering_options.defaultfontfamily, size=xrn_rendering_options.defaultfontsize), 
                                   state=permission_combo)
        
        self.combo.grid(row=0, column=0,sticky="nswe")
        self.combo.current(selection)

        self.combo.bind("<<ComboboxSelected>>", 
                         lambda event, 
                         idstr=rowid: self.on_archive_change(event, idstr))

        self.combo.bind("<Button-1>", lambda event: self.parent.scrollable_frame.disable_scrolling() )
        self.combo.bind("<FocusOut>", lambda event: self.parent.scrollable_frame.disable_scrolling() )
        self.combo.bind("<FocusIn>", lambda event: self.parent.scrollable_frame.enable_scrolling() )
        self.combo.bind("<Button-5>", lambda event: self.block_scroll() )
        self.combo.bind("<Button-4>", lambda event: self.block_scroll() )
        
        subframe_element = tk.Frame( subframe, width=xrn_rendering_options.buttonwidth,height=xrn_rendering_options.rowheight )
        subframe_element.grid(row=0, column=1,sticky="nswe")
        subframe_element.columnconfigure(0,weight=1)
        subframe_element.rowconfigure(0,weight=1)
        subframe_element.grid_propagate(False)
        
        self.button_widget = tk.Button(subframe_element, 
                                        text="show", 
                                        compound="c", padx=0, pady=0,
                                        state=permission, 
                                        font=tkfont.Font(family=xrn_rendering_options.defaultfontfamily, size=xrn_main_window_global.defaultfontsize), 
                                        command=lambda rowid=rowid: self.parent.open_show_archive( rowid ))

        self.button_widget.grid(row=0, column=0,sticky="nswe")


    def get_content(self):
        return self.combo.get()

    def block_scroll( self ):
        return "break"

    def on_archive_change(self, event, idstr):
        for item in self.items:
            if self.get_content() == item["display"]:
                self.field["selection"] = self.get_content()
                self.parent.update_combobox_jlayout( json.dumps(self.field), idstr, "archive" )


def render_layout( jlayout_str ):

    global xrn_main_window_global
    global root_tk_global


    current_geometry = None
    if xrn_main_window_global is not None:
        current_geometry = xrn_main_window_global.get_geometry()
        xrn_main_window_global.hide()
        xrn_main_window_global.destroy()

    xrn_main_window_local = xrn_main_window(root_tk_global, jlayout_str, current_geometry)
    xrn_main_window_global = xrn_main_window_local
    xrn_main_window_global.show()
    root_tk_global.update_idletasks()


def stop_thread_layout ( ) :

    global xrn_main_window_global
    xrn_main_window_global.stop_process()

def reload_pre_thread_layout():
    global xrn_main_window_global
    xrn_main_window_global.load_pre_thread_layout()


def open_text_popup( text ):
     popup_window_local = tk.Toplevel(  )
     popup_window_local.title("info window")
     popup_window_local.geometry("800x400")
     popup_window_local.attributes('-type', 'dialog')
     text_area = tk.scrolledtext.ScrolledText(popup_window_local, wrap=tk.WORD, width=50, height=15)
     text_area.pack(padx=10, pady=10, fill=tk.BOTH, expand=True)
     text_area.insert(tk.INSERT, text)
     text_area.config(state=tk.DISABLED)     
     text_area.see("end")

def layout_store_results_thread ( ):
    global xrn_main_window_global
    slayout_local = xrn_main_window_global.jlayout_str

    file_path = get_from_layout ( slayout_local, "layout_store_results_thread_path_id" )["fields"][2]["display"]
    obj = []
    obj.append( get_from_layout ( slayout_local, "renamed_archive_id_thread_response_id" ) )

    try :
        with open( file_path , "w" ) as f :
            f.write( json.dumps(obj) )
            open_text_popup( "file written" )
    except :
        open_text_popup( "file not written" )


